#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdint.h>
#include "des/libdes.h"

#define POWER32 4294967296ULL
#define PLAINTEXT_64BIT_BLOCKS 1
#define KEY_64BIT_BLOCKS 1
#define TOTAL_BLOCKS 2
//#define UINT64_MAX 1844674587454754874073 
int const  BLOCK_SIZE = (sizeof(uint64_t)*8);
uint64_t get_random64();

 char so[65]; //-- buffer used for binary represent---

void des_wrapper(uint64_t* output, uint64_t* X){

 unsigned char* plaintext = (unsigned char*) malloc(8 * sizeof(unsigned char));
 unsigned char* key = (unsigned char*) malloc(8 * sizeof(unsigned char));
 unsigned char* cipher = (unsigned char*) malloc(8 * sizeof(unsigned char));
 output[0] = 0;
  
 int i;
 for(i = 0; i < 8 ; i++){
   plaintext[i] = X[0]>>i;
}
 for(i = 0; i < 8 ; i++)
   key[i] = X[1]>>i;
 
 des_encipher(plaintext,cipher,key);

 for(i = 7; i>= 0; i--){
  output[0] |= cipher[i];
  if(i!=0)
   output[0] <<= 8;

 }
}


/* test if f(X+Y)+f(X)+f(Y)=0 over the cube 				    */
/* where X and Y are taken randomly                                         */

unsigned long cube_and_linearity_test(uint64_t* X, uint64_t* Y, uint64_t* XY, void(*cipher)(uint64_t*,uint64_t*),uint64_t* output, uint64_t* mask, int cube_diamension )
{
  unsigned long i,j1,j2,j3,j;
  uint64_t  cube_size = 1 << cube_diamension;
  //int index = cube_diamension / BLOCK_SIZE; 
  int reminder = cube_diamension % BLOCK_SIZE;
  int block_mask = UINT64_MAX << reminder; 

  //assing random bits for plaintext and key
  for (i = 0; i < TOTAL_BLOCKS ; i++){
    X[i]  = get_random64();
    Y[i]  = get_random64();
    XY[i] = X[i]^Y[i]; //Xor used for linearity test 
  }
  j = 0;
  for (i = 0; i < cube_size; i++){

    X[0] &= block_mask;
    X[0] |= i;
    cipher(output, X);
    output[0] &= mask[0]; // == *ouput &= mask
    j1  = hamming_mod2(output[0]);

    printf("i = %d \n", i);
    printf("X = ");
    pbX(X);
    printf("j1 = %d \n", j1);

    Y[0] &= block_mask;
    Y[0] |= i;
    cipher(output, Y);
    output[0] &= mask[0];
    j2  = hamming_mod2(output[0]);

    printf("Y = ");
    pbX(Y);
    printf("j2 = %d \n", j2);

    XY[0] &= block_mask;
    XY[0] |= i;
    cipher(output, XY);
    output[0] &= mask[0]; 
    j3  = hamming_mod2(output[0]);

    printf("XY = ");
    pbX(XY);
    printf("j3 = %d \n", j3);

    j ^= j1^j2^j3; //linear test 
  }
  return (j);
}

//------------ Sample test used to allocate memory for cube_and_linearity_test and perform the test n times to assure linearity
int sample_test(void(*cipher)(uint64_t*,uint64_t*), uint64_t* mask){

  int sample = 100,i,count=0;
  uint64_t* X  = (uint64_t*)malloc((PLAINTEXT_64BIT_BLOCKS + KEY_64BIT_BLOCKS)*sizeof(uint64_t)); //2 blocks of 64 = 128 bits
  uint64_t* Y  = (uint64_t*)malloc((PLAINTEXT_64BIT_BLOCKS + KEY_64BIT_BLOCKS)*sizeof(uint64_t));
  uint64_t* XY = (uint64_t*)malloc((PLAINTEXT_64BIT_BLOCKS + KEY_64BIT_BLOCKS)*sizeof(uint64_t));
  uint64_t* output  = (uint64_t*)malloc(PLAINTEXT_64BIT_BLOCKS*sizeof(uint64_t));


  for (i = 0; i < sample; i++)
        count += cube_and_linearity_test(X,XY,Y,cipher,output,mask,5);

  if (count >0)
    return(1);
  else return(0);

  free(X);
  free(Y);
  free(XY);
  free(output);
}


//------------ function to generate random 64 bits ---------
uint64_t get_random64(){
 unsigned long a = lrand48() % POWER32;
 unsigned long b = lrand48() % POWER32;

 uint64_t z = a ^ ((uint64_t)b << 32) ;
		
return z;
}	
//------------ function compute Hamming weight of 64 bits Modulo 2 ---------
int hamming_mod2( uint64_t DES_AND_mask ){

 int count = 0;
 
  while( DES_AND_mask > 0 )
  {
    if ( DES_AND_mask & 1 ) 
     count ++ ;
  DES_AND_mask >>= 1;
  }
 return (count % 2) ; 
}

//------------ function convert to Binary representation ----------------

char* pBin(uint64_t x, char* so)
{
 char s[65];
 int    i=64;
 s[i--]=0x00;   // terminate string
 do
 { // fill in array from right to left
  s[i--]=(x & 1) ? '1':'0';  // determine bit
  x>>=1;  // shift right 1 bit
 } while( x > 0);
 i++;   // point to last valid character
 sprintf(so,"%s",s+i); // stick it in the temp string string
 return so;
}

//=============== pb ============
void pb(uint64_t x){
   
 int i; 
 for( i = 0; i < 64; i++)
    printf("%llu", (x >> i) & 1);
 printf("\n");

}


void pbX(uint64_t* X){
 int i,j;
 
 for(i = 0; i < TOTAL_BLOCKS; i++){ 

 for( j = 0; j < 64; j++)
    printf("%llu", (X[i] >> j) & 1);
 printf(" ");
}
 printf("\n");
}

//========= main ==============
int main(int argc, char** argv){

 srand48(time(NULL));


  
 unsigned char source[] = "1234567";
 unsigned char key[] = "keykeyr"; 
 unsigned char dest[8];
 des_encipher(source,dest,key);

 uint64_t mask = get_random64() ;
 uint64_t* dest_l = &dest[0];
 
 uint64_t cipher = (uint64_t) dest_l[0];
 uint64_t result = cipher & mask ;
 uint8_t boolean_func = hamming_mod2(result);
 

 printf("=====================\n");
 int i = sample_test(des_wrapper, &mask);

  if (i!=0) printf("\n\n Cube attack is not feasible for this mask!\n\n");
  else{  printf("\n\n Cube attack is feasible for this mask: ");
   pb(mask);
   printf("\n");
  }

 return 0 ;
}// end main
